/**
 pfodSecurity for Arduino
 Parses commands of the form { cmd | arg1 ` arg2 ... } hashCode
 Arguments are separated by `
 The | and the args are optional
 This is a complete paser for ALL commands a pfodApp will send to a pfodDevice

 pfodSecurity is also used to send pfod messages to the pfodApp and add a SipHash code to the end
 of each messge so that the authenticity and correctness of the message can be verified.

 see www.pfod.com.au  for more details and example applications
 see http://www.forward.com.au/pfod/secureChallengeResponse/index.html for the details on the
 128bit security and how to generate a secure secret password

 pfodSecurity adds about 2100 bytes to the program and uses about 400 bytes RAM
 and upto 19 bytes of EEPROM starting from the address passed to init()


 The pfodSecurity parses messages of the form
 { cmd | arg1 ` arg2 ` ... } hashCode
 The hashCode is checked against the SipHash_2_4 using the 128bit secret key.
 The hash input is the message count, the challenge for this connection and the message
 If the hash fails the cmd is set to DisconnectNow ('!')
 It is then the responsability of the calling program to close the connection so another user can connect.

 If the hash passes the message is parsed into the args array
 by replacing '|', '`' and '}' with '/0' (null)

 When the the end of message } is seen
 parse() returns the first byte of the cmd
 getCmd() returns a pointer to the null terminated cmd
 skipCmd() returns a pointer to the first arg (null terminated)
 or a pointer to null if there are no args
 getArgsCount() returns the number of args found.
 These calls are valid until the start of the next msg { is parsed.
 At which time they are reset to empty command and no args.

 (c)2013 Forward Computing and Control Pty. Ltd.
 This code may be freely used for both private and commerical use.
 Provide this copyright is maintained.
 */

#include "pfodSecurity.h"
#include "pfodParser.h"
#include "SipHash_2_4.h"
#include "pfodParserUtils.h"
#include "HexConversionUtils.h"
#include <string.h>

#define CR 13
#define NL 10

//#define DEBUG_RAWIO
//#define DEBUG_AUTHORIZATION
//#define DEBUG_EEPROM_KEY_UPDATE
//#define DEBUG_TIMER
//#define DEBUG_PARSER
//#define DEBUG_ECHO_INPUT
// WARNING do not uncomment this define when connect to the network
// as it sends the expected response as well as the challenge
// ONLY use this via the serial monitor for debugging
//#define DEBUG_DISPLAY_RESPONSE

#define NOT_AUTHORIZING 0
// set in disconnnected
#define AUTHORIZING_START (NOT_AUTHORIZING+1)
// set in connect or from loop if NOT_AUTHORIZING to go from disconnect to AUTHORIZING_START if not call to connect()
#define AUTHORIZING_SENT_CHALLENGE (AUTHORIZING_START+1)
#define AUTHORIZING_SUCCESS (AUTHORIZING_SENT_CHALLENGE+1)
#define AUTHORIZING_CMD '_'

pfodSecurity::pfodSecurity() {
  debugOut = NULL;
  pfod_Base_set = NULL;
  initialization = true;
  idleTimeOut = 0; // never timeout use setIdleTimeOut(seconds); to change
  setIdleTimeoutCalled = false;
  _authorizationTimeout = 10000;// default 10sec
}

/**
 * Set the idle Timeout in seconds  i.e. 60 ==> 60sec ==> 1min
 * Default is 0 i.e. never timeout
 * When set to >0 then if no incoming msg received for this time (in mS), then the
 * parser will return DisconnectNow to disconnect the link
 *
 * Setting this to non-zero value protects against a hacker taking over your connection and just hanging on to it
 * not sending any msgs but not releasing it, so preventing you from re-connecting.
 * when setting to non-zero you can use the pfod re-request time to ask the pfodApp
 * to re-request a menu every so often to prevent the connection timing out
 * while the pfodApp is running. See the pfod Specification for details.
 *
 * Set to >0 to enable the timeout (recommended)
 * NOTE: If you send rawdata at regular intervals (less than the timeout) then the connection will never timeout.
 *
 * Note: this method does NOT change the timeout for ESP8266_AT, use ESP8266_AT.setIdleTimeout( ) to change this from
 * the default 40sec.  A non-zero value is used in the ESP8266_AT because the ESP8266-01 has a link timeout setting.
 */
void pfodSecurity::setIdleTimeout(unsigned long timeout) {
  setIdleTimeoutCalled = true;
  if (pfod_Base_set) {
    pfod_Base_set->_setIdleTimeout(timeout);
    idleTimeOut = 0;
    // ignore this and use pfod_Base idle timeout
#ifdef DEBUG_TIMER
    if (debugOut != NULL) {
      debugOut->println(F("setIdleTimeout() set idleTimeOut to "));
      debugOut->println(idleTimeOut);
    }
#endif
  } else {
    idleTimeOut = timeout * 1000;
#ifdef DEBUG_TIMER
    if (debugOut != NULL) {
      debugOut->println(F("setIdleTimeout() set idleTimeOut to "));
      debugOut->println(idleTimeOut);
    }
#endif
  }
}

/*
 * authorization timeout in secs
 */
void pfodSecurity::setAuthorizationTimeout(unsigned long timeout) {
  _authorizationTimeout = timeout * 1000;
}


/**
 * Call this before calling connect() if you want the debug output
 * to go to some other Stream, other then the communication link
 *
 * debugOut is not written to unless you uncomment one of the #define DEBUG_ settings above
 */
void pfodSecurity::setDebugStream(Print* out) {
  debugOut = out;
  mac.setDebugStream(debugOut);
}

void pfodSecurity::connect(pfod_Base* base_arg) {
  connect(base_arg, F(""), 0);
}

void pfodSecurity::connect(pfod_Base* base_arg,  const __FlashStringHelper *hexKeyPgr, int eepromAddress) {
  pfod_Base_set = base_arg;
  // once pfod_Base_set not null these calls to setIdleTimeout zero the pfodSecurity timeout and leave
  // it to the pfod_Base to handle time out
  if (setIdleTimeoutCalled) { // use the users setting
    setIdleTimeout(idleTimeOut / 1000); // once pfod_Base_set is not null setIdleTimeout passed through
  } else { // set the default
    setIdleTimeout(pfod_Base_set->getDefaultTimeOut()); // once pfod_Base_set is not null setIdleTimeout passed through
  }

  setAuthorizationTimeout(0); // disable authorization timeout idle timeout will let another connection through
  connect(base_arg, base_arg->getRawDataOutput() , hexKeyPgr, eepromAddress);
}


void pfodSecurity::connect(Stream* io_arg) {
  connect(io_arg, io_arg);
}

void pfodSecurity::connect(Stream* io_arg, Print* raw_io_arg) {
  connect(io_arg, raw_io_arg, F(""), 0);
}

void pfodSecurity::connect(Stream* io_arg, const __FlashStringHelper *hexKeyPgr_arg, int eepromAddress_arg) {
  connect(io_arg, io_arg, hexKeyPgr_arg, eepromAddress_arg);
}

void pfodSecurity::init() {
  setAuthorizeState(NOT_AUTHORIZING);
  raw_io = NULL;
  parser.init();
#ifdef DEBUG_PARSER
  if (debugOut != NULL) {
    debugOut->println(F(" pfodSecurity::init() parsing true"));
  }
#endif // DEBUG_PARSER
  parsing = true;
  connectionTimer = 0; // will start on first char recieved
#ifdef DEBUG_TIMER
  if (debugOut != NULL) {
    debugOut->print(F("init() clear connectionTimer to "));
    debugOut->println(connectionTimer);
  }
#endif

  inMsgCount = 0;
  outMsgCount = 0;
  msgHashCount = -1; // stop collecting hash
  outputParserState = pfodParser::pfodWaitingForStart;
}

/**
 * Call when the parser returns !
 * any bytes in the input buffer after ! is returned are dropped
 * any bytes recieved while not parsing are dropped
 */
void pfodSecurity::closeConnection() {
#ifdef DEBUG_AUTHORIZATION
  if (debugOut != NULL) {
    debugOut->println(F(" pfodSecurity::closeConnection() called"));
  }
#endif // DEBUG_AUTHORIZATION
  init();
  if (pfod_Base_set) {
    pfod_Base_set->_closeCurrentConnection(); // just times it out
  }
  parser.closeConnection(); // clears io but that is not a problem
  // also clears out cmd so need to add back ! if needed
}


/**
 * initialize the Security parser
 * args
 * io_arg the Stream pointer to read and write to for pfod messages
 *
 * eepromAddress the starting address in eeprom to save the key and power cycles
 *    amount of eeprom used is (2 bytes power cycles + 1 byte key length + key bytes) ==> 3 + (input hexKey length)/2
 *
 * hexKey  pointer to program memory F("hexString") holding the key
 *  if this key is different from the current one in eeprom the eeprom is updated and the
 *  power cycles are reset to 0xffff
 *  if changing the key suggest you add 2 to your eepromAddress to move on from the previous
 *  one.  The power cycle EEPROM addresses are are written to on each power up
 *
 */
void pfodSecurity::connect(Stream * io_arg, Print * raw_io_arg, const __FlashStringHelper * hexKeyPgr_arg, int eepromAddress_arg) {
  // max key size for this code which uses SipHash is 128 bits (16 bytes) (32 hex digits)
  // if eepromAddress is <0 or hexKeyPtr NULL then no security

  noPassword = false;
  timeSinceLastConnection = 0;
  failedHash = false;
  io = io_arg;
  raw_io_connect_arg = raw_io_arg; // save for enabling raw data on connect
  hexKeyPgr = hexKeyPgr_arg;
  eepromAddress = eepromAddress_arg;
  init();
  //raw_io = raw_io_arg; // set raw_io AFTER init() set after hand shake on first cmd

  setAuthorizeState(AUTHORIZING_START);
  parser.connect(io); // clear disconnect not cmd
#ifdef DEBUG_PARSER
  if (debugOut != NULL) {
    debugOut->println(F(" pfodSecurity::connect() parsing true"));
  }
#endif // DEBUG_PARSER
  parsing = true;
  connectionTimer = _authorizationTimeout;
#ifdef DEBUG_TIMER
  if (debugOut != NULL) {
    debugOut->print(F(" initialize authorization timer to "));
    debugOut->println(connectionTimer);
  }
#endif

  if (debugOut == NULL) {
    debugOut = io; // debugOut is not written to unless you uncomment one of the #define DEBUG_ settings above
  }
  // else create mac if null
  mac.setDebugStream(debugOut);
  char hexKey[mac.maxKeySize * 2 + 2]; // max  32 hexDigits for hex key
  //const char PROGMEM *p = (const char PROGMEM *)hexKeyPgr;
  const char *p = (const char PROGMEM *)hexKeyPgr;
  size_t n = 0;
  while (1) {
    unsigned char c = pgm_read_byte(p++);
    hexKey[n++] = c;
    if (c == 0) {
      n--;
      break; // store terminator
    };
    if (n >= mac.maxKeySize * 2) {
      hexKey[n] = 0; // limit to mac.maxKeySize*2 hexDigits
      break;
    }
  }

  byte keyBytes[mac.maxKeySize];
#ifdef DEBUG_EEPROM_KEY_UPDATE
  if (debugOut != NULL) {
    debugOut->print(F(" Input Key of length "));
    debugOut->print(n);
    debugOut->println();
  }
#endif
  int keyLen = asciiToHex(hexKey, keyBytes, mac.maxKeySize);
#ifdef DEBUG_EEPROM_KEY_UPDATE
  if (debugOut != NULL) {
    debugOut->print(F(" New Key of length "));
    debugOut->print(keyLen);
    debugOut->print(F(" 0x"));
    mac.printBytesInHex(debugOut, keyBytes, keyLen);
    debugOut->println();
  }
#endif
  // now check if key has changed
  byte currentKeyBytes[mac.maxKeySize];
  int currentKeyLen = mac.readSecretKey(currentKeyBytes, eepromAddress + mac.KeyOffset);
#ifdef DEBUG_EEPROM_KEY_UPDATE
  if (debugOut != NULL) {
    debugOut->print(F(" Current Key of length "));
    debugOut->print(currentKeyLen);
    debugOut->print(F(" 0x"));
    mac.printBytesInHex(debugOut, currentKeyBytes, currentKeyLen);
    debugOut->println();
  }
#endif
  boolean keysMatch = true;
  if (keyLen == currentKeyLen) {
    // check bytes
    for (int i = 0; i < (int) keyLen; i++) {
      if (keyBytes[i] != currentKeyBytes[i]) {
        keysMatch = false;
        break;
      }
    }
  } else {
    keysMatch = false;
  }
  if (keysMatch) {
#ifdef DEBUG_EEPROM_KEY_UPDATE
    if (debugOut != NULL) {
      debugOut->println(F(" Keys match do NOT update key"));
    }
#endif
  } else {
#ifdef DEBUG_EEPROM_KEY_UPDATE
    if (debugOut != NULL) {
      debugOut->println(F(" Keys do not match update to new key"));
    }
#endif
    setSecretKey(eepromAddress, keyBytes, keyLen);
  }
#ifdef DEBUG_PARSER
  if (debugOut != NULL) {
    debugOut->println(F(" pfodSecurity::connect() start with connection closed"));
  }
#endif // DEBUG_PARSER
  closeConnection(); // start with connection closed
  if (keyLen == 0) {
    noPassword = true;
    connectionTimer = 0;
#ifdef DEBUG_EEPROM_KEY_UPDATE
    if (debugOut != NULL) {
      debugOut->println(F(" No Password"));
    }
#endif
  }
  mac.init(eepromAddress);
}

/**
 * Return the stream pfodSecurity is writing its output to
 * Don't write directly to this stream
 * unless forceing a disconnect
 */
Stream* pfodSecurity::getPfodAppStream() {
  return io;
}

/**
 * Implement the methods need for print
 */
size_t pfodSecurity::write(uint8_t b) {
  return writeToPfodApp((byte) b);
}

size_t pfodSecurity::write(const uint8_t *buffer, size_t size) {
  size_t rtn = 0;
  for (size_t i = 0; i < size; i++) {
    rtn += writeToPfodApp(buffer[i]);
  }
  return rtn;
}

/**
 * setSecretKey set the key and also sets the power cycles to 65535
 * eepromAddress is starting address len+3 bytes are used
 */
void pfodSecurity::setSecretKey(int eepromAddress, byte * key, int len) {
  int add = eepromAddress + mac.PowerCyclesOffset;
  for (int i = 0; i < 2; i++) {
    mac.eeprom_write(add++, (byte) 0xff);
  }
  add = eepromAddress + mac.KeyOffset;
  mac.eeprom_write(add++, len);
  byte* keyPtr = key;
  for (int i = 0; i < len; i++) {
    mac.eeprom_write(add++, *keyPtr++);
  }
}

/**
 * Return pointer to start of args[]
 */
byte* pfodSecurity::getCmd() {
  return parser.getCmd();
}

/**
 * Return pointer to first arg (or pointer to null if no args)
 *
 * Start at args[0] and scan for first null
 * if argsCount > 0 increment to point to  start of first arg
 * else if argsCount == 0 leave pointing to null
 */
byte* pfodSecurity::getFirstArg() {
  return parser.getFirstArg();
}

/**
 * Return pointer to next arg or pointer to null if end of args
 * Need to call getFirstArg() first
 * Start at current pointer and scan for first null
 * if scanned over a non empty arg then skip terminating null and return
 * pointer to next arg, else return start if start points to null already
 */
byte* pfodSecurity::getNextArg(byte * start) {
  return parser.getNextArg(start);
}

/**
 * Return number of args in last parsed msg
 */
byte pfodSecurity::getArgsCount() {
  return parser.getArgsCount();
}

/**
 * parse
 * Inputs:
 * byte in -- byte read from Serial port
 * Return:
 * return 0 if complete message not found yet
 * else return first char of cmd when see closing }
 * or ignore msg if pfodMaxCmdLen bytes after {
 * On non-zero return args[] contains
 * the cmd null terminated followed by the args null terminated
 * argsCount is the number of args
 *
 * parses
 * { cmd | arg1 ` arg2 ... }
 * save the cmd in args[] replace | with null (\0)
 * then save arg1,arg2 etc in args[] replaceing ` with null
 * count of args saved in argCount
 * on seeing } return first char of cmd
 * if no } seen for pfodMaxCmdLen bytes after starting { then
 * ignore msg and start looking for { again
 *
 * States:
 * before {   parserState == pfodWaitingForStart
 * when   { seen parserState == pfodInMsg
 */

#ifdef DEBUG_TIMER
  unsigned long ms = millis();
#endif

void pfodSecurity::setAuthorizeState(int authState) {
  if (noPassword) {
    authorizing = AUTHORIZING_SUCCESS;
  } else {
    authorizing = authState;
  }
}

/**
 * Write bytes to serial out until reach null '/0'
 * Inputs:
 *  idxPtr - byte* pointer to start of bytes to write
 * return
 */
size_t pfodSecurity::writeToPfodApp(uint8_t* idxPtr) {
  size_t rtn = 0;
  while (*idxPtr != 0) {
    byte b = *idxPtr;
    rtn += writeToPfodApp(b);
    ++idxPtr;
  }
  return rtn;
}

// for SMS need to split between rawData and command responses
// command responses take precedence
/**
 *  write a byte to the connect stream
 *  If the byte is part of a { } msg then add it to the hash
 *  and write the hash after the final }
 *  Raw data, i.e. data outside { } is just passed through
 */
size_t pfodSecurity::writeToPfodApp(uint8_t b) {
  size_t rtn = 0;
  if (authorizing != AUTHORIZING_SUCCESS) {
    // have not completed authorization yet so just consume all output
    // and tell the program that the byte has been handled
    return 1;
  }
  if (!io) {
    return 1;
  }
  // possible outputParserState values are:-
  // pfodWaitingForStart = 0xff;
  // pfodMsgStarted = '{';
  // pfodInMsg = 0;
  if (outputParserState == pfodParser::pfodWaitingForStart) {
    if (b == pfodParser::pfodMsgStarted) {
      // found { while waiting for start
      outputParserState = pfodParser::pfodMsgStarted;
      return 1; // nothing else to do
    } else {
      // NOT in msg started
      // is outside { }
    }
  } else if (outputParserState == pfodParser::pfodMsgStarted) {
    // {} Empty  {@ Language {. Menu {: Update menu/navigation items {^ Navigation input {= Streaming raw data
    // {' String input {# Numeric input {? Single Selection input {* Multiple  {! close connection
    if ((b == '}') || (b == '@') || (b == '.') || (b == ':') || (b == '^') || (b == '=') || (b == '\'') // '
        || (b == '#') || (b == '?') || (b == '*') || (b == '!')) {
      // this is real command
      // initial hash
      if (!noPassword) {
#ifdef DEBUG_DISPLAY_RESPONSE
        if (debugOut != NULL) {
          debugOut->println(F(" init output hash"));
        }
#endif
        mac.initHash();
        mac.putByteToHash('{');
      } else {
        // no password skip hash
      }
      // write the { that started this response
      int rtn_tmp = 0;
      while (!rtn_tmp) {
        // loop here until write succeeds
        rtn_tmp = io->write(pfodParser::pfodMsgStarted); // response
      }
      outputParserState = pfodParser::pfodInMsg; // in msg
    } else {
      // not a real command response so all raw data
      // write out the held back {
      if (raw_io) {
        int rtn_tmp = 0;
#ifdef DEBUG_RAWIO
        if (debugOut != NULL) {
          debugOut->print(F(" raw_io:"));
          debugOut->print((char)pfodParser::pfodMsgStarted);
        }
#endif
        if (authorizing == AUTHORIZING_SUCCESS) {
          connectionTimer = idleTimeOut; // clear timer and set idle timeout when sending raw data
        }
        while (!rtn_tmp) {
          // loop here until write succeeds
          rtn_tmp = raw_io->write(pfodParser::pfodMsgStarted); // raw data
        }
      } else {
#ifdef DEBUG_RAWIO
        if (debugOut != NULL) {
          //  debugOut->print(F("raw_io null "));
        }
#endif
      }

      if (b == pfodParser::pfodMsgStarted) {
        // {{ go back to pfodMsgStarted
        // hold back second { until next char read
        outputParserState = pfodParser::pfodMsgStarted; // finished
        return 1;
      } else {
        // not a real command just raw data
        outputParserState = pfodParser::pfodWaitingForStart; // finished
        // write this byte below
      }
    }
  } else if (outputParserState == pfodParser::pfodInMsg) {
    // check for } below
  } // else pfodMsgEnd but that should not happen for output

#ifdef DEBUG_DISPLAY_RESPONSE
  if (debugOut != NULL) {
    debugOut->write(b);
  }
#endif

  if (outputParserState == pfodParser::pfodInMsg) {
    while (!rtn) {
      // loop here until write succeeds
      rtn = io->write(b); // response
    }
    if (!noPassword) {
      // add it to the hash
      mac.putByteToHash(b);
    }
    if (b == pfodParser::pfodMsgEnd) {
      // found closing }
      outputParserState = pfodParser::pfodWaitingForStart; // finished
      if (noPassword) {
        io->flush();
      } else { // have password
        // create hash and send
#ifdef DEBUG_DISPLAY_RESPONSE
        if (debugOut != NULL) {
          debugOut->println(F(" send hash"));
          debugOut->print(F(" out msg Count "));
          debugOut->println(outMsgCount);
        }
#endif
        mac.putLongToHash(outMsgCount);
        outMsgCount++; // increment for next msg out
        for (int i = 0; i < pfodMAC::challengeByteSize; i++) {
          mac.putByteToHash(challenge[i]);
        }
        mac.finishHash();
#ifdef DEBUG_DISPLAY_RESPONSE
        if (debugOut != NULL) {
          debugOut->println(F(" add hashResult"));
        }
#endif
        // convert to hex
        hexToAscii((const unsigned char *) mac.getHashedResult(), mac.msgHashByteSize, (char*) msgHashBytes,
                   Msg_Hash_Size + 1);
        for (int i = 0; i < Msg_Hash_Size;) {
          i += io->write(msgHashBytes[i]);
          // note if output is full may lose byte here and hash at the other end will fail
          // so increment i from return from write so will loop here until write of all bytes is successful
        }
        io->flush(); // push result
      }
    }
  } else { // not pfodMsgStarted or pfodInMsg so must be waiting for start
    // NOTE if was pfodMsgStarted then returned above
    // raw data
    if (raw_io) {
#ifdef DEBUG_RAWIO
      if (debugOut != NULL) {
        //debugOut->print(F(" raw_io:"));
        debugOut->print((char)b);
      }
#endif
      if (authorizing == AUTHORIZING_SUCCESS) {
          connectionTimer = idleTimeOut; // clear timer and set idle timeout when sending raw data
      }
      while (!rtn) {
        // loop here until write succeeds
        rtn = raw_io->write(b); // response
      }
    } else {
#ifdef DEBUG_RAWIO
      if (debugOut != NULL) {
        //   debugOut->print(F("raw_io null "));
      }
#endif
      rtn = 1;
    }
  }
  return rtn;
}

/**
 * parseLong
 * will parse between  -2,147,483,648 to 2,147,483,647
 * No error checking done.
 * will return 0 for empty string, i.e. first byte == null
 *
 * Inputs:
 *  idxPtr - byte* pointer to start of bytes to parse
 *  result - long* pointer to where to store result
 * return
 *   byte* updated pointer to bytes after skipping terminator
 *
 */
byte* pfodSecurity::parseLong(byte * idxPtr, long * result) {
  return parser.parseLong(idxPtr, result);
}

byte pfodSecurity::parse() {
  const byte DisconnectNow = '!';
  unsigned long deltaMillis = 0; // clear last result
  unsigned long thisMillis = millis(); // do this just once to prevent getting different answers from multiple calls to millis()

  if (thisMillis != lastMillis) {
    // we have ticked over
    // calculate how many millis have gone past
    deltaMillis = thisMillis - lastMillis; // note this works even if millis() has rolled over back to 0
    lastMillis = thisMillis;
  }

  timeSinceLastConnection += deltaMillis;
  if (timeSinceLastConnection > MAX_ELAPSED_TIME) {
    // limit it
    timeSinceLastConnection = MAX_ELAPSED_TIME;
  }

#ifdef DEBUG_TIMER
  if (debugOut != NULL) {
    if (millis() > ms) {
      debugOut->print(F("Connection Timer:"));
      debugOut->println(connectionTimer);
      ms = millis() + 1000;
    }
  }
#endif
  byte cmd = 0;
  byte in = 0;

  if (!io) {
    return 0; // no stream to read
  }
  // else not in getPasswordMode
  if ((!parsing) || (!mac.isValid())) {  // parsing and powerCycles > 0
    while (io->available()) {
      in = io->read();
#ifdef DEBUG_ECHO_INPUT
      if (debugOut != NULL) {
        if ((in == NL) || (in == CR)) {
          debugOut->println();
        } else {
          debugOut->print((char)in);
        }
      }
#endif
    }
#ifdef DEBUG_PARSER
    if (debugOut != NULL) {
      debugOut->print(F(" !parsing || !mac.isValid  cmd=DisconnectNow"));
    }
#endif
    cmd = DisconnectNow;
  } else {
    if ((deltaMillis != 0) && (connectionTimer > 0)) {
      connectionTimer -= deltaMillis;
      if (connectionTimer <= 0) {
#ifdef DEBUG_PARSER
        if (debugOut != NULL) {
          debugOut->print(F(" timer timed out"));
        }
#endif
        cmd = DisconnectNow;
      }
    }
    // parsing
    if (cmd != DisconnectNow) {
#ifdef DEBUG_ECHO_INPUT
      if (debugOut != NULL) {
        if (io->available()) {
          debugOut->println(F("start io->available() "));
        }
      }
#endif
      //if (io->available()) {
      while ((io->available()) && (!cmd)) {
        in = io->read();
#ifdef DEBUG_ECHO_INPUT
        if (debugOut != NULL) {
          debugOut->print(F(" read "));
          debugOut->println((char)in);
        }
#endif
        if (in == 0xff) {
          if (pfod_Base_set) {
            closeConnection();
            parser.setCmd('!');
            cmd = '!';
            continue; // will break
          } else {
            // ignore
            continue;
          }
        } // else
        if ((!noPassword) && (msgHashCount >= 0)) {
          msgHashBytes[msgHashCount++] = in;
#ifdef DEBUG_DISPLAY_RESPONSE
          if (debugOut != NULL) {
            debugOut->print(F(" hashHex:"));
            debugOut->println((char)in);
          }
#endif
          if (msgHashCount == Msg_Hash_Size) {
            msgHashBytes[Msg_Hash_Size] = 0; //terminate
            // check hash
            msgHashCount = -1;
#ifdef DEBUG_DISPLAY_RESPONSE
            if (debugOut != NULL) {
              debugOut->print(F(" checkMsgHash: inMsgCount:"));
              debugOut->print(inMsgCount);
              debugOut->print(" ");
              mac.printBytesInHex(debugOut, mac.getHashedResult(), Msg_Hash_Size_Bytes);
              debugOut->println();
              debugOut->println((char*)msgHashBytes);
            }
#endif
            if (mac.checkMsgHash(msgHashBytes, Msg_Hash_Size_Bytes)) {
#ifdef DEBUG_ECHO_INPUT
              if (debugOut != NULL) {
                debugOut->println((char)in);
              }
#endif
              cmd = *parser.getCmd(); // reset cmd from parser
              inMsgCount++;
            } else {
#ifdef DEBUG_ECHO_INPUT
              if (debugOut != NULL) {
                debugOut->println(F("Hash failed "));
              }
#endif
              failedHash = true;
              cmd = DisconnectNow;
            }
          }
        } else {
          // if not completed authorization and  authorization timer zero start it now
          if ((authorizing != AUTHORIZING_SUCCESS) && (connectionTimer == 0)) {
            connectionTimer = _authorizationTimeout;
#ifdef DEBUG_TIMER
            if (debugOut != NULL) {
              debugOut->print(F(" reset connection timer to "));
              debugOut->print(connectionTimer);
              debugOut->println(F(" because == 0 and not success"));
            }
#endif
          }
          if (authorizing == NOT_AUTHORIZING) {
            setAuthorizeState(AUTHORIZING_START);
          }
#ifdef DEBUG_ECHO_INPUT
          if (debugOut != NULL) {
            if ((in == NL) || (in == CR)) {
              debugOut->println();
            } else {
              debugOut->print((char)in);
            }
          }
#endif
          cmd = parser.parse(in);
          if (cmd == DisconnectNow) {
            // act on it even without hash
          } else if (authorizing == AUTHORIZING_SUCCESS) {
#ifdef DEBUG_AUTHORIZATION
            if (debugOut != NULL) {
              debugOut->println(F(" AUTHORIZING_SUCCESS"));
            }
#endif
            if (noPassword) {
              // skip this
            } else {
#ifdef DEBUG_AUTHORIZATION
              if (debugOut != NULL) {
                debugOut->println(F(" with password"));
              }
#endif
              if (parser.getParserState() == parser.pfodMsgStarted) {
                // initial hash
#ifdef DEBUG_AUTHORIZATION
                if (debugOut != NULL) {
                  debugOut->println(F(" init hash"));
                }
#endif
                mac.initHash();
                mac.putByteToHash(parser.pfodMsgStarted);
              } else if (parser.getParserState() == parser.pfodInMsg) {
                mac.putByteToHash(in);
              } else if (parser.getParserState() == parser.pfodMsgEnd) {
                if ((cmd == AUTHORIZING_CMD) && (strlen((const char*) parser.getCmd()) == 1) && (parser.getArgsCount() == 0)) {
                  if (authorizing == AUTHORIZING_SENT_CHALLENGE) {
                    // pfodApp trying to start a new connection but we think we are have already started one
                    // i.e. got {_} and send challenge and are waiting for response
                    // note real response will NOT have zero args
                    // so other side just sending {_} followed by {_}
                    // just disconnnect now to resync
#ifdef DEBUG_AUTHORIZATION
                    if (debugOut != NULL) {
                      debugOut->println(F(" got {_} response to AUTHORIZING_SENT_CHALLENGE -- disconnect."));
                    }
#endif
                    cmd = DisconnectNow;
                  } else {
                    // got through challenge and response other side just disconnect and reconnected
                    // else call disconnect /connect
                    closeConnection();
                    connect(io, raw_io_connect_arg, hexKeyPgr, eepromAddress); // set authorizing to AUTHORIZING_START
                    parser.setCmd(AUTHORIZING_START); // connect initializes parser
                    // Note NO hash for {_} msg
                  }
                } else {
#ifdef DEBUG_DISPLAY_RESPONSE
                  if (debugOut != NULL) {
                    debugOut->println(F(" clear cmd"));
                  }
#endif
                  mac.putByteToHash(in);
                  cmd = 0; // clear cmd
                  msgHashCount = 0;
#ifdef DEBUG_DISPLAY_RESPONSE
                  if (debugOut != NULL) {
                    debugOut->print(F(" msg Count "));
                    debugOut->println(inMsgCount);
                  }
#endif
                  mac.putLongToHash(inMsgCount);
                  for (int i = 0; i < pfodMAC::challengeByteSize; i++) {
                    mac.putByteToHash(challenge[i]);
                  }
                  mac.finishHash();
                }
              } else {
                // ignore
              }

            }
          }
        }
      }
    }
  } // end  if (!parsing)

  if (cmd != 0) {
    if (cmd == DisconnectNow) {
#ifdef DEBUG_PARSER
      if (debugOut != NULL) {
        debugOut->println(F(" cmd == DisconnectNow "));
      }
#endif
    } else {
#ifdef DEBUG_PARSER
      if (debugOut != NULL) {
        debugOut->print(F(" found cmd "));
        debugOut->println((char)cmd);
      }
#endif
    }
  }

  if ((cmd != DisconnectNow) && (cmd != 0)) {
    if (authorizing == AUTHORIZING_START) { // will not get this state if noPassword see setAuthorizeState()
      if ((cmd == AUTHORIZING_CMD) && (strlen((const char*) parser.getCmd()) == 1) && (parser.getArgsCount() == 0)) {
        // send challenge
        setAuthorizeState(AUTHORIZING_SENT_CHALLENGE);
        connectionTimer = _authorizationTimeout; // reset timer
#ifdef DEBUG_TIMER
        if (debugOut != NULL) {
          debugOut->print(F("AUTHORIZING_SENT_CHALLENGE reset connection timer to "));
          debugOut->println(connectionTimer);
        }
#endif
        // get challenge
        long mS = (long) timeSinceLastConnection;
        if (failedHash) {
          mS = -mS;
        }
        mac.buildChallenge(challenge, mS);
        // start again
        timeSinceLastConnection = 0;
        failedHash = false;
        // add the hash type
        challenge[pfodMAC::challengeByteSize] = 2;
        // send challenge
        io->print(F("{"));
        io->print(AUTHORIZING_CMD);
        mac.printBytesInHex(io, challenge, pfodMAC::challengeByteSize + 1);
        io->print(F("}"));
        io->flush(); // push result
#ifdef DEBUG_DISPLAY_RESPONSE
        if (debugOut != NULL) {
          debugOut->println(F("hashed result"));
          mac.printBytesInHex(debugOut, mac.getHashedResult(), pfodMAC::challengeByteSize);
          debugOut->println();
        }
#endif
        cmd = 0; // finished with this command
      } else {
        // command not correct drop connection
#ifdef DEBUG_PARSER
        if (debugOut != NULL) {
          debugOut->println(F(" challenge request missing"));
        }
#endif
        failedHash = true;
        cmd = DisconnectNow;
      }
    } else if (authorizing == AUTHORIZING_SENT_CHALLENGE) {
      // looking for = cmd
      if ((cmd == AUTHORIZING_CMD) && (strlen((const char*) parser.getCmd()) == 1) && (parser.getArgsCount() == 1)) {
        // check response
        if (mac.checkMsgHash((const byte*) parser.getFirstArg(), pfodMAC::challengeHashByteSize)) {
          cmd = 0; //
          setAuthorizeState(AUTHORIZING_SUCCESS);
          connectionTimer = idleTimeOut; // clear timer and set idle timeout
#ifdef DEBUG_TIMER
          if (debugOut != NULL) {
            debugOut->print(F("AUTHORIZING_SUCCESS reset connection timer to "));
            debugOut->println(connectionTimer);
          }
#endif
#ifdef DEBUG_PARSER
          if (debugOut != NULL) {
            debugOut->println(F(" response matched, initialized idle timout"));
          }
#endif
          io->print(F("{"));
          io->print(AUTHORIZING_CMD);
          io->print(F("}"));
          io->flush();
          cmd = 0; // finished with this command
        } else {
#ifdef DEBUG_PARSER
          if (debugOut != NULL) {
            debugOut->println(F(" response did not match"));
          }
#endif
          failedHash = true;
          cmd = DisconnectNow;
        }
      } else { // not = or not one arg
        cmd = DisconnectNow;
      }
    } else if (authorizing == AUTHORIZING_SUCCESS) {
      // either no password OR finished auth
      if ((cmd == AUTHORIZING_CMD) && (strlen((const char*) parser.getCmd()) == 1) && (parser.getArgsCount() == 0)) {
        // got {_}
        if (noPassword) {
#ifdef DEBUG_AUTHORIZATION
          if (debugOut != NULL) {
            debugOut->println(F(" no password just send back {}"));
          }
#endif
          //no password just send back {}
          io->print(F("{}"));
          io->flush();
          cmd = 0;
          parser.setCmd(0);
        } else {
          // have password should not get this
#ifdef DEBUG_AUTHORIZATION
          if (debugOut != NULL) {
            debugOut->println(F(" Authorized with password but got {_}"));
          }
#endif
          failedHash = true;
          cmd = DisconnectNow;
        }
      } // else not {_}
    }
  }

  if ((cmd == DisconnectNow) || (cmd == '!') || (cmd == AUTHORIZING_CMD)) {
    if (cmd == AUTHORIZING_CMD) {
#ifdef DEBUG_AUTHORIZATION
      if (debugOut != NULL) {
        debugOut->println();
        debugOut->println(F(" found cmd {_  disconnect since invalid"));
      }
#endif
    } else if (cmd == '!') {
#ifdef DEBUG_PARSER
      if (debugOut != NULL) {
        debugOut->println();
        debugOut->println(F(" parsing true and found cmd {!} so return DisconnectNow and set parsing false was true"));
      }
#endif
    }
    cmd = DisconnectNow;
#ifdef DEBUG_PARSER
    if (debugOut != NULL) {
      debugOut->println(F(" set parsing false"));
    }
#endif
    parsing = false;
    msgHashCount = -1;
    setAuthorizeState(NOT_AUTHORIZING);
    connectionTimer = 0; //stop timer
#ifdef DEBUG_TIMER
    if (debugOut != NULL) {
      debugOut->print(F(" clear connection timer to "));
      debugOut->print(connectionTimer);
      debugOut->println(F(" because cmd set to DisconnectNow"));
    }
#endif
    parser.setCmd(DisconnectNow);
  } else {
    if (cmd != 0) {
      if ((authorizing == AUTHORIZING_SUCCESS) && (cmd != 0)) {
        // got another command reset idle timeout
        connectionTimer = idleTimeOut; // clear timer and set idle timeout
#ifdef DEBUG_TIMER
        if (debugOut != NULL) {
          debugOut->print(F("Got another cmd reset connection timer to "));
          debugOut->println(connectionTimer);
        }
#endif
        // and set raw_io
        raw_io = raw_io_connect_arg; // enable raw data now
        // read the next 8 bytes for hash
#ifdef DEBUG_PARSER
        if (debugOut != NULL) {
          debugOut->println();
          debugOut->print(F(" found cmd "));
          debugOut->println((char)cmd);
        }
#endif
      }
    }
  }
#ifdef DEBUG_PARSER
  if (debugOut != NULL) {
    if (cmd != 0) {
      if (cmd != DisconnectNow) {
        debugOut->print(F(" found cmd "));
        debugOut->println((char)cmd);
      } else {
        debugOut->print(F(" found cmd "));
        debugOut->println(F(" !"));
      }
    }
  }
#endif
  if (cmd == DisconnectNow) { // == '!'
    closeConnection(); // close connection now
    parser.setCmd(DisconnectNow);
  }
  return cmd;
}

